package com.pijiu.compose.demo00

import android.os.Bundle
import android.util.Log
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue

/**
 * Jetpack Compose(4)——重组
 * 讲解了重组的一些特性，
 * 如何确定重组的范围，
 * 以及重组的中的类型稳定性概念，
 * 以及如何提升非稳定类型在重组过程中的性能。
 *
 * https://www.cnblogs.com/joy99/p/18113680
 */
class Demo02Activity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            StableTest()
        }
    }
}

//经过 Compose 编译器处理后的 Composable 代码在对 State 进行读取时，能够自动建立关联，
// 在运行过程中，当 State 变化时， Compose 会找到关联的代码块标记为 Invalid,
// 在下一渲染帧到来之前，Compose 触发重组并执行 invalid 代码块， invalid 代码块即下一次重组的范围。
// 能够被标记为 Invalid 的代码必须是非 inline 且无返回值的 Composable 函数或 lambda。
// （换句话说，只有这类代码才可以在 非invalid 时被跳过不需要重复执行）
//
//需要注意的是，重组的范围，与只能跳过并不冲突，确定了重组范围，会调用对应的组件代码，
// 但是当参数没有变化时，在运行时不会真正执行，会跳过本次重组。
@Composable
fun RecompositionTest() {
    Column {
        Box {
            v("RecompositionTest - 1")
            Column {
                v("RecompositionTest - 2")
                var name by remember {
                    mutableStateOf("周瑜")
                }
                Button(onClick = {
                    name = "小乔"
                }) {
                    v("RecompositionTest - 3")
                    Text(text = "改名")
                }
                Text(text = "鲁班")
                Text(text = name)
            }
        }
        Box {
            v("RecompositionTest - 4")
        }
        Card {
            v("RecompositionTest - 5")
        }
    }
}



//前面，Composable 状态变化触发重组，状态变化基于 equals 比较结果，这是不准确的。
// 准确地说：只有当比较的状态对象，是稳定的，才能通过 equals 比较结果确定是否重组。
// 什么叫稳定的？还是看一个例子：

//上面讲了稳定与不稳定的概念，然而实际开发中，我们经常会根据业务自定义 data class,
// 难道用了 Compose， 虽然 Kotlin 编码规范，强调尽量使用 val, 但是还是要根据实际业务，
// 使用 var 来定义可变属性。对于这种类型，我们可以为其添加 @Stable 注解，让编译器将其视为稳定类型。
// 从而发挥智能重组的作用，提升重组的性能。
//这样，Hero 即便使用 var 声明属性，它作为参数传入 Composable 中，只要对象引用没变，都不会触发重组。
// 所以具体什么时候使用该注解，还需要根据需求灵活使用。
//除了 @Stable，Compose 还提供了另一个类似的注解 @Immutable，与 @Stable
// 不同的是，@Immutable 用来修饰的类型应该是完全不可变的。而 @Stable 可以用在函数、属性等更多场景。
// 使用起来更加方便，由于功能叠加，未来 @Immutable 有可能会被移除，建议优先使用 @Stable。
//@Stable
data class Hero(var name: String) //接下来，我们将 Hero 这个类做点改变，将其属性声明由 var 变成 val：

val shangDan = Hero("吕布")

@Composable
fun StableTest() {
    var greeting by remember {
        mutableStateOf("hello, 鲁班")
    }

    Column {
        v("invoke --> 1")
        Text(text = greeting)
        Button(onClick = {
            greeting = "hello, 鲁班大师"
        }) {
            Text(text = "搞错了，是鲁班大师")
        }
        ShangDan(shangDan)
    }
}

@Composable
fun ShangDan(hero: Hero) {
    v("invoke --> 2")
    Text(text = hero.name)
}


private fun v(msg: String) {
    Log.e("pijiu", msg)
}

//其实是在因为此前，用 var 声明 Hero 类的属性时，Hero 类被 Compose 编译器认为是不稳定类型。
// 即有可能，我们传入的参数引用没有变化，但是属性被修改过了，而 UI 又确实需要显示修改后的最新值。
// 而当用 val 声明属性了，Compose 编译器认为该对象，只要对象引用不要变，那么这个对象就不会发生变化，
// 自然 UI 也就不会发生变化，所以就跳过了这次重组。
//
//常用的基本数据类型以及函数类型(lambda)都可以称得上是稳定类型，它们都不可变。
// 反之，如果状态是可变的，那么比较 equals 结果将不再可信。在遇到不稳定类型时，
// Compose 的抉择是宁愿牺牲一些性能，也总好过显示错误的 UI。
